﻿using System;
using System.Web;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

using LongNet.Util;

namespace LongNet.Web
{
    /// <summary>
    /// A helper class to manage to http cookies for convenience.
    /// This class cannot be inherited.
    /// </summary>
    public static class HttpCookies
    {
        #region Fields

        /// <summary>
        /// Represents a value which indicates that an cookie should never expire.
        /// </summary>
        public static readonly DateTime Infinite = DateTime.MaxValue;

        /// <summary>
        /// Represents a value which indicates that an cookie should expire when the browser is closed.
        /// </summary>
        public static readonly TimeSpan InBrowserProcess = TimeSpan.Zero;

        #endregion

        #region Constructor

        static HttpCookies()
        {
        }

        #endregion

        #region Methods

        #region Set

        /// <summary>
        /// Sets a cookie value by name and <see cref="F:HttpCookies.InBrowserProcess"/>, the cookie expires 
        /// when web browser process ends.
        /// </summary>
        /// <param name="name">The name of the cookie.</param>
        /// <param name="value">The value of the cookie.</param>
        public static void Set(string name, string value)
        {
            Set(null, name, value, InBrowserProcess, DateTime.MinValue);
        }

        /// <summary>
        /// Sets a cookie value using the domain, name and <see cref="F:HttpCookies.InBrowserProcess"/>, the cookie expires 
        /// when web browser process ends.
        /// </summary>
        /// <param name="domain">The domain of the cookie.</param>
        /// <param name="name">The name of the cookie.</param>
        /// <param name="value">The value of the cookie.</param>
        public static void Set(string domain, string name, string value)
        {
            Set(domain, name, value, InBrowserProcess, DateTime.MinValue);
        }

        /// <summary>
        /// Sets a cookie value using the name and expiration.
        /// </summary>
        /// <param name="name">The name of the cookie.</param>
        /// <param name="value">The value of the cookie.</param>
        /// <param name="expiration">The interval in which the cookie expires and is removed from the browser.</param>
        public static void Set(string name, string value, TimeSpan expiration)
        {
            Set(null, name, value, expiration, DateTime.MinValue);
        }

        /// <summary>
        /// Sets a cookie value using the name and expiration.
        /// </summary>
        /// <param name="name">The name of the cookie.</param>
        /// <param name="value">The value of the cookie.</param>
        /// <param name="expiration">The time at which the cookie expires and is removed from the browser.</param>
        public static void Set(string name, string value, DateTime expiration)
        {
            Set(null, name, value, TimeSpan.MinValue, expiration);
        }

        /// <summary>
        /// Sets a cookie value using the domain, name and expiration.
        /// </summary>
        /// <param name="domain">The domain of the cookie.</param>
        /// <param name="name">The name of the cookie.</param>
        /// <param name="value">The value of the cookie.</param>
        /// <param name="expiration">The interval in which the cookie expires and is removed from the browser.</param>
        public static void Set(string domain, string name, string value, TimeSpan expiration)
        {
            Set(domain, name, value, expiration, DateTime.MinValue);
        }

        /// <summary>
        /// Sets a cookie value using the domain, name and expiration.
        /// </summary>
        /// <param name="domain">The domain of the cookie.</param>
        /// <param name="name">The name of the cookie.</param>
        /// <param name="value">The value of the cookie.</param>
        /// <param name="expiration">The time at which the cookie expires and is removed from the browser.</param>
        public static void Set(string domain, string name, string value, DateTime expiration)
        {
            Set(domain, name, value, TimeSpan.MinValue, expiration);
        }

        /// <summary>
        /// Sets a cookie value using the domain, name and expiration.
        /// </summary>
        /// <param name="domain">The domain of the cookie.</param>
        /// <param name="name">The name of the cookie.</param>
        /// <param name="value">The value of the cookie.</param>
        /// <param name="validFor">The interval in which the cookie expires and is removed from the browser.</param>
        /// <param name="expiresAt">The time at which the cookie expires and is removed from the browser.</param>
        private static void Set(string domain, string name, string value, TimeSpan validFor, DateTime expiresAt)
        {
            name.ThrowsIfNullOrEmpty("name", "The name is null or empty.");

            var context = HttpContext.Current;
            if (context != null)
            {
                var cookie = context.Request.Cookies[name];
                if (cookie == null)
                {
                    cookie = new HttpCookie(name);
                }

                cookie.Value = LongNet.Util.Utils.UrlEncode(value);

                var expiration = expiresAt > DateTime.MinValue ? expiresAt : DateTime.Now.AddTicks(validFor.Ticks);
                if (validFor != TimeSpan.Zero)
                {
                    cookie.Expires = expiration;
                }

                if (!string.IsNullOrEmpty(domain) && WebUtils.Host.IndexOf(domain) > -1)
                {
                    cookie.Domain = domain;
                }

                //cookie.Path = "/";

                context.Response.AppendCookie(cookie);
            }
        }

        #endregion

        #region Get

        /// <summary>
        /// Gets a cookie by name.
        /// </summary>
        /// <param name="name">The name of cookie.</param>
        public static string Get(string name)
        {
            name.ThrowsIfNullOrEmpty("name", "The name is null or empty.");

            var context = HttpContext.Current;
            if (context != null
                && context.Request.Cookies != null)
            {
                var value = context.Request.Cookies[name];

                if (value != null)
                    return LongNet.Util.Utils.UrlDecode(context.Request.Cookies[name].Value);
            }

            return string.Empty;
        }

        #endregion

        #region Remove/RemoveMultiple/Clear

        /// <summary>
        /// Removes a cookie by name.
        /// </summary>
        /// <param name="name">The name of cookie.</param>
        public static void Remove(string name)
        {
            Remove(null, name);
        }

        /// <summary>
        /// Removes a cookie by domain and name.
        /// </summary>
        /// <param name="domain">The domain of the cookie.</param>
        /// <param name="name">The name of cookie.</param>
        public static void Remove(string domain, string name)
        {
            name.ThrowsIfNullOrEmpty("name", "The name is null or empty.");

            var context = HttpContext.Current;
            if (context != null)
            {
                var cookie = new HttpCookie(name, string.Empty);
                cookie.Expires = DateTime.Now.AddYears(-1);

                if (!string.IsNullOrEmpty(domain) && WebUtils.Host.IndexOf(domain) > -1)
                {
                    cookie.Domain = domain;
                }

                //cookie.Path = "/";
                context.Response.AppendCookie(cookie);
            }
        }

        /// <summary>
        /// Removes multiple objects from the <see cref="System.Web.Caching.Cache"/> object.
        /// </summary>
        /// <param name="names">The names references the cookie values.</param>
        public static void RemoveMultiple(params string[] names)
        {
            RemoveMultiple(null, names);
        }

        /// <summary>
        /// Removes multiple objects from the <see cref="System.Web.Caching.Cache"/> object.
        /// </summary>
        /// <param name="domain">The domain of the cookie.</param>
        /// <param name="names">The names references the cookie values.</param>
        public static void RemoveMultiple(string domain, params string[] names)
        {
            names.ThrowsIfNullOrEmpty("names", "The names is null or empty.");

            Array.ForEach(names, (name) => Remove(domain, name));
        }

        /// <summary>
        /// Removes all cookie items.
        /// </summary>
        public static void Clear()
        {
            Clear(null);
        }

        /// <summary>
        /// Removes all cookie items by the specified domain.
        /// </summary>
        /// <param name="domain">The domain of the cookie.</param>
        public static void Clear(string domain)
        {
            var context = HttpContext.Current;
            if (context != null
                && context.Request.Cookies != null)
            {
                List<string> names = new List<string>();
                IEnumerator enumerator = context.Request.Cookies.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    names.Add((string)enumerator.Current);
                }

                names.ForEach((name) => Remove(null, name));

                if (!string.IsNullOrEmpty(domain))
                {
                    names.ForEach((name) => Remove(domain, name));
                }
            }
        }

        #endregion

        #endregion
    }
}
